package org.example.hrpc.rpc.core.impl;

import io.netty.channel.Channel;
import org.example.hrpc.common.URL;
import org.example.hrpc.common.exception.RemotingException;
import org.example.hrpc.common.exception.RpcException;
import org.example.hrpc.rpc.client.TransportClient;
import org.example.hrpc.rpc.client.TransportClientFactory;
import org.example.hrpc.rpc.core.AbstractProtocol;
import org.example.hrpc.rpc.core.Exporter;
import org.example.hrpc.rpc.core.Invocation;
import org.example.hrpc.rpc.core.Invoker;
import org.example.hrpc.rpc.server.TransportServer;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


/**
 * dubbo protocol support.
 */
public class DefaultProtocol extends AbstractProtocol {

    public static final String NAME = "hrpc";

    public static final int DEFAULT_PORT = 20880;

    private static volatile DefaultProtocol INSTANCE;

    private static Object MONITOR = new Object();

    /**
     * <host:port,Exchanger>
     */
    private final Map<String, Object> referenceClientMap = new ConcurrentHashMap<>();



    public Collection<Invoker<?>> getInvokers() {
        return Collections.unmodifiableCollection(invokers);
    }

    @Override
    public int getDefaultPort() {
        return DEFAULT_PORT;
    }

    @Override
    public Invoker<?>  getInvoker(Channel channel, Invocation inv) throws RemotingException {
        InetSocketAddress addr = (InetSocketAddress)channel.localAddress();
        int port = addr.getPort();
        // 计算serviceKey，格式为groupName/serviceName:ServiceVersion:port
        String serviceKey = serviceKey(port, inv.getTargetServiceUniqueName());
        // 从exporterMap中查找与serviceKey对应的DefaultExporter对象
        // 服务导出过程中会将<serviceKey, Exporter>映射关系存储到exporterMap中
        DefaultExporter<?> exporter = (DefaultExporter<?>) exporterMap.getExport(serviceKey);

        if (exporter == null) {
            throw new RemotingException("Not found exported service: " + serviceKey + " in " + exporterMap.getExporterMap().keySet() + ", may be version or group mismatch " + " ");
//                            ", channel: consumer: " + channel + " --> provider: " + channel.getLocalAddress() +
//                            ", message:" + getInvocationWithoutData(inv));
        }

        return exporter.getInvoker();
    }

    @Override
    public <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException {
        // 这里假设仅考虑导出服务，不考虑服务注册的逻辑，服务注册的逻辑先不实现，或者在另外的逻辑中实现
        // 在上层的逻辑中实现服务注册比较好，这里仅考虑服务导出的逻辑
        // export service.
        URL url = invoker.getUrl();
        String host = url.getHost();
        int port = url.getPort();
        InetSocketAddress address = InetSocketAddress.createUnresolved(host, port);
        String key = serviceKey(port, url.getPath());
        DefaultExporter<T> exporter = new DefaultExporter<T>(invoker, key, exporterMap);
        logger.info("Create Exporter: {} for Invoker: {}", exporter, invoker);
        exporterMap.addExportMap(key, exporter);

        // 启动服务器
        openServer(address);

        return exporter;
    }

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        DefaultInvoker<T> invoker = new DefaultInvoker<>(type, url, getClient(url), invokers);
        invokers.add(invoker);
        return invoker;
    }

    private void openServer(InetSocketAddress address) {
        // find server.
        TransportServer server = serverMap.get(address);
        if (server == null) {
            synchronized (this) {
                server = serverMap.get(address);
                if (server == null) {
                    serverMap.put(address.getHostName()+":"+address.getPort(), createServer(address));
                }
            }
        } else {
            // server supports reset, use together with override
            // 由于一个端口只能启动一个实例，所以当某个端口上已有服务实例时，需要调用reset方法重置服务器的一些设置
//            server.reset(null);
        }
    }

    private TransportServer createServer(InetSocketAddress socketAddress) {
        TransportServer server = null;
        String host = socketAddress.getHostName();
        int port = socketAddress.getPort();
        server = TransportServer.createServer(host, port);
        logger.info("Success create TransportServer: [{}:{}]", host, port);
        return server;
    }


    private TransportClient getClient(URL url) {
        TransportClientFactory clientFactory = TransportClientFactory.INSTANCE;
        String host = url.getHost();
        int port = url.getPort();
        TransportClient client = null;
        try {
            client = clientFactory.createClient(host, port);
            logger.info("Success create TransportClient:[{}:{}]", host, port);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return client;
    }

    private String serviceKey(int port, String serviceName) {
        String key =  port + ":" + serviceName;
        logger.info("Serivce Key: {}", key);
        return key;
    }

}
